package com.pretty.common.groovy;

import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyCodeSource;
import groovy.lang.GroovyResourceLoader;
import groovyjarjarasm.asm.Opcodes;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.InnerClassNode;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.classgen.Verifier;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.SourceUnit;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.*;
import java.security.*;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Map;

/**
 * 只要功能是重新加载更改过的.class文件，达到热替换的作用
 *
 * @author banana
 */

public class HotSwapGroovyClassLoader extends GroovyClassLoader {
    private static final URL[] EMPTY_URL_ARRAY = new URL[0];
    private static int scriptNameCounter = 1000000;
    private GroovyResourceLoader resourceLoader;
    private CompilerConfiguration config;

    public HotSwapGroovyClassLoader() {
        this(Thread.currentThread().getContextClassLoader());
    }

    public HotSwapGroovyClassLoader(ClassLoader loader) {
        this(loader, null);
    }

    public HotSwapGroovyClassLoader(ClassLoader parent, CompilerConfiguration config, boolean useConfigurationClasspath) {
        super(parent, config, useConfigurationClasspath);
        this.config = config;
    }

    public HotSwapGroovyClassLoader(ClassLoader loader, CompilerConfiguration config) {
        this(loader, config, true);
    }

    public GroovyResourceLoader getResourceLoader() {
        return this.resourceLoader;
    }

    public Class parseClass(File file) throws CompilationFailedException, IOException {
        return super.parseClass(file);
    }

    public Class parseClass(final String text, final String fileName) throws CompilationFailedException {
        GroovyCodeSource gcs = (GroovyCodeSource) AccessController.doPrivileged(new PrivilegedAction() {
            public GroovyCodeSource run() {
                return new GroovyCodeSource(text, fileName, "/groovy/script");
            }
        });
        gcs.setCachable(false);
        return this.parseClass(gcs);
    }

    public Class parseClass(String text) throws CompilationFailedException {
        return this.parseClass(text, "script" + System.currentTimeMillis() + Math.abs(text.hashCode()) + ".groovy");
    }

    public synchronized String generateScriptName() {
        ++scriptNameCounter;
        return "script" + scriptNameCounter + ".groovy";
    }


    public Class parseClass(GroovyCodeSource codeSource) throws CompilationFailedException {
        return this.parseClass(codeSource, codeSource.isCachable());
    }

    protected PermissionCollection getPermissions(CodeSource codeSource) {
        Object perms;
        try {
            try {
                perms = super.getPermissions(codeSource);
            } catch (SecurityException var6) {
                perms = new Permissions();
            }

            ProtectionDomain e = (ProtectionDomain) AccessController.doPrivileged(new PrivilegedAction() {
                public ProtectionDomain run() {
                    return this.getClass().getProtectionDomain();
                }
            });
            PermissionCollection myPerms = e.getPermissions();
            if (myPerms != null) {
                Enumeration elements = myPerms.elements();

                while (elements.hasMoreElements()) {
                    ((PermissionCollection) perms).add((Permission) elements.nextElement());
                }
            }
        } catch (Throwable var7) {
            perms = new Permissions();
        }

        ((PermissionCollection) perms).setReadOnly();
        return (PermissionCollection) perms;
    }

    public Class defineClass(String name, byte[] b) {
        return super.defineClass(name, b, 0, b.length);
    }

    protected Class getClassCacheEntry(String name) {
        if (name == null) {
            return null;
        } else {
            Map var2 = this.classCache;
            synchronized (this.classCache) {
                return (Class) this.classCache.get(name);
            }
        }
    }

    protected void setClassCacheEntry(Class cls) {
        Map var2 = this.classCache;
        synchronized (this.classCache) {
            this.classCache.put(cls.getName(), cls);
        }
    }

    protected void removeClassCacheEntry(String name) {
        Map var2 = this.classCache;
        synchronized (this.classCache) {
            this.classCache.remove(name);
        }
    }

    public void addURL(URL url) {
        super.addURL(url);
    }

    protected boolean isRecompilable(Class cls) {
        return super.isRecompilable(cls);
    }

    public Class loadClass(String baseDir, String name, boolean lookupScriptFiles, boolean preferClassOverScript) throws ClassNotFoundException, CompilationFailedException {
        Class cls = this.getClassCacheEntry(name);
        boolean recompile = this.isRecompilable(cls);
        if (!recompile) {
            return cls;
        } else {
            ClassNotFoundException last = null;
            SecurityManager sm1 = System.getSecurityManager();
            if (sm1 != null) {
                String ioe = name.replace('/', '.');
                int source = ioe.lastIndexOf(46);
                if (source != -1 && !ioe.startsWith("sun.reflect.")) {
                    sm1.checkPackageAccess(ioe.substring(0, source));
                }
            }
            if (cls != null && preferClassOverScript) {
                return cls;
            } else {
                if (lookupScriptFiles) {
                    try {
                        Class ioe1 = this.getClassCacheEntry(name);
                        if (ioe1 != cls) {
                            Class source2 = ioe1;
                            return source2;
                        }
                        this.addURL(new File(baseDir).toURI().toURL());
                        this.addClasspath(baseDir);
                        cls = Class.forName(name, true, this);
                        return cls;
                    } catch (IOException var17) {
                        last = new ClassNotFoundException("IOException while opening groovy source: " + name, var17);
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        if (cls == null) {
                            this.removeClassCacheEntry(name);
                        } else {
                            this.setClassCacheEntry(cls);
                        }

                    }
                }

                if (cls == null) {
                    if (last == null) {
                        throw new AssertionError(true);
                    } else {
                        throw last;
                    }
                } else {
                    return cls;
                }
            }
        }
    }

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return this.loadClass(name, false);
    }

    protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
        return this.loadClass(name, true, true, resolve);
    }

    protected long getTimeStamp(Class cls) {
        return Verifier.getTimestamp(cls);
    }

    private static String decodeFileName(String fileName) {
        String decodedFile = fileName;

        try {
            decodedFile = URLDecoder.decode(fileName, "UTF-8");
        } catch (UnsupportedEncodingException var3) {
            System.err.println("Encountered an invalid encoding scheme when trying to use URLDecoder.decode() inside of the HotSwapGroovyClassLoader.decodeFileName() method.  Returning the unencoded URL.");
            System.err.println("Please note that if you encounter this error and you have spaces in your directory you will run into issues.  Refer to GROOVY-1787 for description of this bug.");
        }

        return decodedFile;
    }

    private static boolean isFile(URL ret) {
        return ret != null && ret.getProtocol().equals("file");
    }

    private static File getFileForUrl(URL ret, String filename) {
        String fileWithoutPackage = filename;
        if (filename.indexOf(47) != -1) {
            int index = filename.lastIndexOf(47);
            fileWithoutPackage = filename.substring(index + 1);
        }

        return fileReallyExists(ret, fileWithoutPackage);
    }

    private static File fileReallyExists(URL ret, String fileWithoutPackage) {
        File path;
        try {
            path = new File(ret.toURI());
        } catch (URISyntaxException var9) {
            path = new File(decodeFileName(ret.getFile()));
        }

        path = path.getParentFile();
        if (path.exists() && path.isDirectory()) {
            File file = new File(path, fileWithoutPackage);
            if (file.exists()) {
                File parent = file.getParentFile();
                String[] arr$ = parent.list();
                int len$ = arr$.length;

                for (int i$ = 0; i$ < len$; ++i$) {
                    String child = arr$[i$];
                    if (child.equals(fileWithoutPackage)) {
                        return file;
                    }
                }
            }
        }

        return null;
    }

    private URL getSourceFile(String name, String extension) {
        String filename = name.replace('.', '/') + "." + extension;
        URL ret = this.getResource(filename);
        return isFile(ret) && getFileForUrl(ret, filename) == null ? null : ret;
    }

    protected boolean isSourceNewer(URL source, Class cls) throws IOException {
        long lastMod;
        if (isFile(source)) {
            String conn = source.getPath().replace('/', File.separatorChar).replace('|', ':');
            File file = new File(conn);
            lastMod = file.lastModified();
        } else {
            URLConnection conn1 = source.openConnection();
            lastMod = conn1.getLastModified();
            conn1.getInputStream().close();
        }

        long classTime = this.getTimeStamp(cls);
        return classTime + (long) this.config.getMinimumRecompilationInterval() < lastMod;
    }

    public void addClasspath(final String path) {
        AccessController.doPrivileged(new PrivilegedAction() {
            public Void run() {
                URI newURI;
                try {
                    newURI = new URI(path);
                    newURI.toURL();
                } catch (URISyntaxException var9) {
                    newURI = (new File(path)).toURI();
                } catch (MalformedURLException var10) {
                    newURI = (new File(path)).toURI();
                } catch (IllegalArgumentException var11) {
                    newURI = (new File(path)).toURI();
                }

                URL[] urls = HotSwapGroovyClassLoader.this.getURLs();
                URL[] e = urls;
                int len$ = urls.length;

                for (int i$ = 0; i$ < len$; ++i$) {
                    URL url = e[i$];

                    try {
                        if (newURI.equals(url.toURI())) {
                            return null;
                        }
                    } catch (URISyntaxException var12) {
                        throw new RuntimeException(var12);
                    }
                }

                try {
                    HotSwapGroovyClassLoader.this.addURL(newURI.toURL());
                    return null;
                } catch (MalformedURLException var8) {
                    throw new RuntimeException(var8);
                }
            }
        });
    }

    public Class[] getLoadedClasses() {
        Map var1 = this.classCache;
        synchronized (this.classCache) {
            Collection values = this.classCache.values();
            return (Class[]) values.toArray(new Class[values.size()]);
        }
    }

    public void clearCache() {
        Map var1 = this.classCache;
        synchronized (this.classCache) {
            this.classCache.clear();
        }

        var1 = this.sourceCache;
        synchronized (this.sourceCache) {
            this.sourceCache.clear();
        }
    }

    private static class TimestampAdder extends CompilationUnit.PrimaryClassNodeOperation implements Opcodes {
        private static final HotSwapGroovyClassLoader.TimestampAdder INSTANCE = new HotSwapGroovyClassLoader.TimestampAdder();

        private TimestampAdder() {
        }

        protected void addTimeStamp(ClassNode node) {
            if (node.getDeclaredField("__timeStamp") == null) {
                FieldNode timeTagField = new FieldNode("__timeStamp", 4105, ClassHelper.long_TYPE, node, new ConstantExpression(Long.valueOf(System.currentTimeMillis())));
                timeTagField.setSynthetic(true);
                node.addField(timeTagField);
                timeTagField = new FieldNode("__timeStamp__239_neverHappen" + String.valueOf(System.currentTimeMillis()), 4105, ClassHelper.long_TYPE, node, new ConstantExpression(Long.valueOf(0L)));
                timeTagField.setSynthetic(true);
                node.addField(timeTagField);
            }

        }

        public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException {
            if ((classNode.getModifiers() & 512) <= 0) {
                if (!(classNode instanceof InnerClassNode)) {
                    this.addTimeStamp(classNode);
                }

            }
        }
    }
}